home *** CD-ROM | disk | FTP | other *** search
- Newsgroups: comp.lang.c
- Path: new-news.sprintlink.net!eskimo!scs
- From: scs@eskimo.com (Steve Summit)
- Subject: Re: How to use assert( )
- X-Nntp-Posting-Host: eskimo.com
- Message-ID: <DpnqFE.E8v@eskimo.com>
- Sender: news@eskimo.com (News User Id)
- Organization: schmorganization
- References: <4kc3k7$dur@orion.cybercom.net>
- Date: Wed, 10 Apr 1996 17:40:26 GMT
-
- In article <4kc3k7$dur@orion.cybercom.net>, nield@cybercom.net writes:
- > I'm just starting my first project big enough to split among many
- > people, and from the vague explanations I've heard, assert is supposed
- > to be a usefull way to cause errors when someone passes your code bad
- > values.
-
- Indeed (as long as we understand that "someone" is one of your
- fellow programmers, and *not* one of the program's users).
-
- An assertion (which is what C's assert() macro implements)
- allows you to document the assumptions made by a piece of code,
- and furthermore to arrange that the code test its assumptions
- as it runs. Usually, these assumptions boil down to "the rest
- of the program is written correctly," and in a large program,
- particularly one worked on by many people, this is of course
- *not* always a valid assumption, and so is eminently worth
- testing.
-
- Using the assert() macro is simple (as it should be, if
- assertions are to be conveniently used): after including
- its header:
-
- #include <assert.h>
-
- you sprinkle the code with "calls" to
-
- assert(expr)
-
- where expr is an expression that "should be" true, that is, that
- evaluates to nonzero if the particular assumption is indeed met.
-
- Judicious assertions are unquestionably a Good Thing, but I
- suppose it's possible to go overboard with them, particularly
- when you've just discovered them. [The verb "sprinkle" above is
- perhaps injudicious.] There's no point in a single module's
- checking the sanity of the entire world; it need only check the
- particular conditions upon which it depends. Typically, the
- conditions checked by an assertion are those which, if false,
- would cause later statements in the module containing the
- assertion to fail (by crashing, damaging data structures,
- generating incorrect output, etc.). The assertion test is
- clearly preferable (even though what a failed assertion typically
- does is deliberately crash your program) because it documents
- what's going on reasonably clearly, while the later crash (if the
- assertion didn't catch the problem first) might be much more
- mysterious.
-
- Rudimentary examples would be
-
- assert(n != 0);
- average = sum / n;
-
- in a function which computes an average and is documented as only
- working on non-empty arrays, and
-
- assert(p != NULL);
-
- in a function which does a lot of work with a pointer p.
- As a more detailed example, just yesterday I found myself
- writing code which ended up looking something like this:
-
- if(list->next == NULL)
- {
- /* several lines special-casing a 1-element list */
- return;
- }
-
- /* several lines to preprocess list->item */
-
- assert(list->next != NULL);
- for(lp = list->next; lp != NULL; lp = list->next)
- process(lp->item);
-
- In this code, a list with exactly one element is treated
- completely differently, and a list with more than one element has
- its first element treated specially ("preprocessed") before the
- remainder of the list is processed. When I first found myself
- writing
-
- for(lp = list->next; lp != NULL; lp = list->next)
-
- I said, "Wait a minute. That's a weird loop. I almost never
- write loops like that. What If list->next starts out null?"
- Furthermore, it happened that the code would *not* have behaved
- correctly if none of the "process" steps were taken.
-
- Looking farther up the page, we see that it "can't happen" that
- list->next is ever null by the time the loop in question is
- reached, but we have to look just enough farther up, and the
- intervening preprocessing code is complicated enough, and in
- fact in the actual code the expression which I simplify here as
- (list->next == NULL) is complicated enough, that we're not being
- unduly paranoid if we contemplate the possibility that, sometime
- down the road, a modification of some kind could vertently or
- inadvertently cause the assumption to be violated. Thus the call
- to assert() just before the loop.
-
- (If you're curious, the code in question extracts revisions from
- RCS files by constructing a list of delta texts to apply. The
- first "delta text" is the head revision in its entirety, which is
- the file we want if that's the revision we're extracting, and is
- the basis to apply the remaining deltas to otherwise.)
-
- It's important to note that assertions should only be used to
- test for programming errors. You should never use an assertion
- to test that a function like fopen() or malloc() has been called
- successfully, or that a user has supplied correct input.
- Real-world errors (files not being openable, sufficient memory
- not being available, users stubbornly refusing to provide
- syntactically correct input) are properly handled with explicit
- code which detects the error, issues its own detailed message,
- and cleans up and continues if appropriate. In other words,
- assertions are properly used to detect unexpected failures caused
- by programming errors, *not* to detect real-world failures, which
- in a properly-written program should not be unexpected.
-
- Once a program is "working," some programmers take that as a sign
- that it contains no more bugs, and worry that testing the
- assertions wastes too much CPU time, or that they might one day
- be embarrassed to have a customer ask, "What does `assertion
- failed in line 5341 of temporary_kludges.c' mean, and how happy
- should I be to have spent $2000 on a program with at least 5341
- lines of temporary kludges?" These programmers can assuage their
- fears by defining the macro NDEBUG, which causes the remaining
- calls to assert() in their code to be replaced with code which
- does nothing (neither using any text space or CPU time, nor of
- course validating any assumptions, either).
-
- One last point: last I heard, the language lawyers over in
- comp.std.c had determined that the operand of the assert()
- macro is *not* quite the same as, say, the controlling Boolean
- expression of an if statement, such that the call
-
- assert(p);
-
- where p is a pointer, might not be strictly conforming.
- It's therefore said to be safer to call
-
- assert(p != NULL);
-
- (or, if you prefer, assert(p != 0)), which happens to be the sort
- of thing I prefer writing anyway.
-
- Steve Summit
- scs@eskimo.com
-